package dsl

import dominio.Agenda
import dominio.AgendaListener
import dominio.Aviso
import dominio.AvisoLlamada
import dominio.AvisoMail
import dominio.AvisoSMS
import dominio.Evento
import dominio.IAgendaListener
import org.joda.time.LocalTime

class AgendaDSL {
	
	/**
	 * Indica los minutos
	 */
	def Integer m(Integer m) {
		m
	}
	
	
	/**
	 * Crea una LocalTime con hora dada y minutos en 00
	 */
	def LocalTime h(Integer h) {
		new LocalTime(h, 0)
	}


	/**
	 * Crea una LocalTime con hora y minutos dados
	 */
	def LocalTime h(Integer h, Integer m) {
		new LocalTime(h, m)
	}
	
	
	/**
	 * Crea un Evento que inicia a una hora dada, con un nombre dado
	 */
	def Evento operator_minus(LocalTime hora, String nombre) {
		new Evento(hora, nombre)
	}
	
	
	/**
	 * Operador para agregar un Aviso al Evento
	 */
	def Evento operator_greaterThan(Evento evento, Aviso avisos) {
		evento.avisos += avisos
		evento
	}
	
	
	/**
	 * Crea un AvisoMail con el mensaje msj
	 */
	def AvisoMail mail(String msj) {
		new AvisoMail(msj)
	}


	/**
	 * Crea un AvisoSMS con el mensaje msj
	 */
	def AvisoSMS sms(String msj) {
		new AvisoSMS(msj)
	}
	
	
	/**
	 * Crea un AvisoLlamada con el mensaje msj
	 */
	def AvisoLlamada llamada(String msj) {
		new AvisoLlamada(msj)
	}
	
	
	/**
	 * Agrega a la agenda un evento dado
	 */
	def operator_tripleLessThan(Agenda agenda, Evento eventos) {
		agenda.eventos += eventos
		agenda
	}

	
	/**
	 * Representa una dupla (LocalTime, AgendaListener)
	 */
	def Dupla<LocalTime, AgendaListener> operator_and(LocalTime h, AgendaListener listener) {
		new Dupla(h, listener)
	}

	
	/**
	 * Hace un tick en la agenda
	 */
	def operator_upTo(Agenda agenda, Dupla<LocalTime, AgendaListener> params) {
		agenda.tick(params.first, params.second)
	}
	
	
	/**
	 * Ejemplo funcional del uso del DSL
	 */
	def primerEjemplo(Agenda agenda, IAgendaListener listener) {
		
		agenda <<< (9.h(15.m) - "Levantarse"
			> "Lavarse la cara".llamada
			> "Pegarse una ducha".llamada			
			> "Desayunar".sms => [
				numero = 1503927890
			])
		
		agenda <<< (15.h - "Gimnasio")
			
		agenda <<< (18.h - "Termina Objetos III" 
					> "Ir a la parada del bondi".mail)
					
		
		agenda <<< (21.h(30.m) - "Cena" 
					> "Lavarse las manos".mail => [
						destino = "enseidler@gmail.com"
						asunto = "Manos sucias"
					]
					> "Poner la mesa".llamada => [
						numero = 1512934819
					])
		
		agenda .. (17.h && listener)
		agenda .. (18.h && listener)
		agenda .. (20.h && listener)
	}
	

	def static void main(String[] args) {
	
		/* Se corre el ejemplo */		
		new AgendaDSL => [
			val agenda = new Agenda
			val listener = new IAgendaListener
			primerEjemplo(agenda, listener)
			
			/* Se imprimen los avisos que "saltaron" hasta las 20hrs */
			listener.avisosAcontecidos.forEach[a |
				println(a.mensaje)
			]
			
		]
			
	}
			
	
}

		
		/**
		 * Guia de uso para AgendaDSL 
		 */
		/*
		 * Se crea la Agenda
		 * val agenda = ...
		 * 
		 * Agregar evento a la agenda
		 * agenda <<< (<aca va el evento>)
		 * 
		 * Se crea un evento
		 * hora.h(minutos.m) "-" ("aca va el nombre del evento")
		 * 
		 * Agregar un aviso al evento
		 * evento ">" (<aca va el aviso>)
		 * 
		 * Se crea un aviso
		 * mail/sms/llamada("aca va el mensaje del aviso")
		 * "aca va el mensaje".mail/sms/llamada
		 * 
		 * 
		 * //REGLAS DE PARENTESIS:
		 * 
		 * Los unicos lugares donde los parentesis son necesarios son:
		 * 		-Cuando se quiere expresar una hora con minutos > 0
		 * 			Ej: 23.h(40.m)
		 * 		-Cuando se agrega un evento a la agenda
		 * 			Ej: agenda <<< (<el evento>)
		 * 		-Cuando se hace un tick en una agenda
		 * 			Ej: agenda .. (<hora && listener>)
		 * 
		 * //REGLAS DE HORARIO:
		 * 
		 * La hora puede expresarse de dos maneras:
		 * 		- 12.h (12 en punto)
		 * 		- 12.h(23.m) (12 horas y 23 minutos) 
		 */
		 
		 
class Dupla<A, B> {
	
	@Property A first
	@Property B second
	
	new() {}
	
	
	new(A first, B second) {
		this.first = first
		this.second = second 
	}
	
}